#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/ip_icmp.h> /* struct icmphdr definition */
#include <netinet/in.h> /* IPPROTO_ICMP definition */
#include <netinet/ip.h>
#include <sys/time.h>
#include <sys/types.h>
#include <string.h>

#define __ICMP_ECHO_ID 14080

/* return 0 if is not a valid icmp reply, unless != 0 */
static int valid_icmp_reply(char* packet_reply, int icmp_counter, int icmp_echo_id, struct in_addr* ip)
{
	struct iphdr *ip_reply = (struct iphdr *) packet_reply;
	struct icmphdr *icmp_reply = (struct icmphdr *) (packet_reply + sizeof(struct iphdr));


	if ( memcmp( (void *) ip, (void *) &ip_reply->saddr, sizeof(struct in_addr) ) != 0 ) { /* I don't want this packet */
		    printf("[check_ping]I don't want this package from : %s\n", inet_ntoa(ip_reply->saddr));
		    return 0;
	}	    
	
	if (icmp_reply->type != ICMP_ECHOREPLY) {
		printf("[check_ping]packet is not a echo reply [%d]\n",icmp_reply->type );
		return 0;
	}
	if (icmp_reply->un.echo.id != htons(icmp_echo_id)) {
		printf("[check_ping]echo id : %d, it should be : %d\n", icmp_reply->un.echo.id, icmp_echo_id);
		return 0;
	}
	/* if it receives an packet is because we have connectivity, it doesn't matter if was in any queue 
	   so it doesn't check the sequence 
	  
	if (icmp_reply->un.echo.sequence != htons(icmp_counter)) {
		printf("echo sequence : %d, it should be : %d\n", ntohs(icmp_reply->un.echo.sequence), icmp_counter);
		return 0;
	}
	*/

	/* valid icmp reply */
	return 1;
}

static int in_cksum(unsigned short *buf, int sz)
{
	int nleft = sz;
	int sum = 0;
	unsigned short *w = buf;
	unsigned short ans = 0;

	while (nleft > 1) {
		sum += *w++;
		nleft -= 2;
	}

	if (nleft == 1) {
		*(unsigned char *) (&ans) = *(unsigned char *) w;
		sum += ans;
	}

	sum = (sum >> 16) + (sum & 0xFFFF);
	sum += (sum >> 16);
	ans = ~sum;
	return (ans);
}

static int open_socket_icmp()
{
	int sock;
	sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if ( sock < 0 ) {
		perror("socket:");
		return -1;
	}
	return sock;
}

int check_connec_ping(struct in_addr ip)
{
	struct sockaddr_in ipto;
	struct sockaddr_in ipfrom;
	socklen_t fromlen = sizeof(ipfrom);
	struct icmphdr* icmp_packet;
	char packet[128];
	char packet_reply[128];
	struct icmphdr icmp_reply;
	int res,res_select;
	int sock;
	fd_set rfds;
	struct timeval tv;
	int icmp_counter = 0;
	int lost_packets = 0;
       

	memset(&ipto,0,sizeof(struct sockaddr_in));
	icmp_packet = (struct icmphdr*) packet;
	memset(packet,0,128);

	memcpy(&ipto.sin_addr, &ip, sizeof(struct in_addr));

	sock = open_socket_icmp();
	if (sock<0)
		return -1;

	printf("[check_ping]Ping ... %s\n", inet_ntoa(ip));	

	icmp_packet->type = ICMP_ECHO;
	icmp_packet->code = 0; 
	icmp_packet->un.echo.id = htons(__ICMP_ECHO_ID);

	do {
		icmp_packet->checksum = 0;
		icmp_packet->un.echo.sequence = htons(icmp_counter);
		icmp_packet->checksum = in_cksum((u_short *)icmp_packet, sizeof(struct icmphdr));

		res = sendto(sock, packet, sizeof(struct icmphdr), 0, (struct sockaddr *)&ipto, sizeof(struct sockaddr));
		if (res < 0){
			perror("sento");
			return -1;
		}
		/* we wait for a bit*/
		FD_ZERO(&rfds);
		FD_SET(sock, &rfds);
		tv.tv_sec = 1; /* max time we wait for a reply */
		tv.tv_usec = 0;
		res_select = select (sock + 1, &rfds, NULL, NULL, &tv);
		if (res_select < 0){
			perror("select");
			return -1;
		}
		if (FD_ISSET(sock, &rfds)) { /* we have an answer */
			
			res = recvfrom(sock, packet_reply, sizeof(packet_reply), 0, (struct sockaddr *)&ipfrom, &fromlen);
			if (res < 0){
				perror("recvfrom");
				return -1;
			}
			//printf("byte read from recvfrom : %d from %s\n", res, inet_ntoa(ipfrom.sin_addr));
			if (!valid_icmp_reply(packet_reply, icmp_counter, __ICMP_ECHO_ID, &ipto.sin_addr)) { /* reply not valid */
			       	printf("[check_ping[there are traffic in the network...ignoring\n");
				lost_packets++;
			}			
		        else {
				printf("[check_ping]Reply from %s\n",inet_ntoa(ip));
				if (lost_packets > 0)
					lost_packets--;
			}
		}
		else { /* we didn't have an answer */
			lost_packets++;
	       	}
		printf("[check_ping]losts packet : %d\n", lost_packets);
 
		sleep(1); /* we wait 1 second until the next ping */
		icmp_counter++;
		
	} while(lost_packets < 10);

	if (close(sock))
		fprintf(stderr,"Error closing socket\n");
	
	return 0;
}


/* return the number of seconds it needs to resolve one name. */
/* -1 if an error happens or >20 if it was not possible to resolve the name (no connectivity) */
static int __resolv_name()
{
	/* name it will try to resolv */
	char name[]="www.google.com";
	struct hostent * p_host;
	time_t t1, t2;
	
	time(&t1);
	if (t1==-1){
		fprintf(stderr,"error getting time\n");
	}
	p_host = gethostbyname(name); /* it tries to resolve each 5 seconds over 20 seconds */
	time(&t2);
	if (t2==-1){
		fprintf(stderr,"error getting time\n");
	}
        if (p_host == NULL) { /* an error */
		/* herror("error resolving name: "); */
		return 21;
	}
	return t2 - t1;
}

/* return a value between 0 and 100 */
/* 0 : No connectivity. 100 : Perfect connectivity */
int check_connec_dns()
{

	int result;
	result = __resolv_name();
	if (result < 0 || result > 20) return 0;
	if (result == 20) return 20;
	result = 20 - result;
	return result * 5;
}
